"""Integration test for all light call combinations.

Tests that LightCall handles all possible light operations correctly
including RGB, color temperature, effects, transitions, and flash.
"""

import asyncio
from typing import Any

from aioesphomeapi import LightState
from aioesphomeapi.model import ColorMode
import pytest

from .types import APIClientConnectedFactory, RunCompiledFunction


@pytest.mark.asyncio
async def test_light_calls(
    yaml_config: str,
    run_compiled: RunCompiledFunction,
    api_client_connected: APIClientConnectedFactory,
) -> None:
    """Test all possible LightCall operations and combinations."""
    async with run_compiled(yaml_config), api_client_connected() as client:
        # Track state changes with futures
        state_futures: dict[int, asyncio.Future[Any]] = {}
        states: dict[int, Any] = {}

        def on_state(state: Any) -> None:
            states[state.key] = state
            if state.key in state_futures and not state_futures[state.key].done():
                state_futures[state.key].set_result(state)

        client.subscribe_states(on_state)

        # Get the light entities
        entities = await client.list_entities_services()
        lights = [e for e in entities[0] if e.object_id.startswith("test_")]
        assert len(lights) >= 3  # Should have RGBCW, RGB, and Binary lights

        rgbcw_light = next(light for light in lights if "RGBCW" in light.name)
        rgb_light = next(light for light in lights if "RGB Light" in light.name)
        binary_light = next(light for light in lights if "Binary" in light.name)

        # Test color mode encoding: Verify supported_color_modes contains actual ColorMode enum values
        # not bit positions. This is critical - the iterator must convert bit positions to actual
        # ColorMode enum values for API encoding.

        # RGBCW light (rgbww platform) should support RGB_COLD_WARM_WHITE mode
        assert ColorMode.RGB_COLD_WARM_WHITE in rgbcw_light.supported_color_modes, (
            f"RGBCW light missing RGB_COLD_WARM_WHITE mode. Got: {rgbcw_light.supported_color_modes}"
        )
        # Verify it's the actual enum value, not bit position
        assert ColorMode.RGB_COLD_WARM_WHITE.value in [
            mode.value for mode in rgbcw_light.supported_color_modes
        ], (
            f"RGBCW light has wrong color mode values. Expected {ColorMode.RGB_COLD_WARM_WHITE.value} "
            f"(RGB_COLD_WARM_WHITE), got: {[mode.value for mode in rgbcw_light.supported_color_modes]}"
        )

        # RGB light should support RGB mode
        assert ColorMode.RGB in rgb_light.supported_color_modes, (
            f"RGB light missing RGB color mode. Got: {rgb_light.supported_color_modes}"
        )
        # Verify it's the actual enum value, not bit position
        assert ColorMode.RGB.value in [
            mode.value for mode in rgb_light.supported_color_modes
        ], (
            f"RGB light has wrong color mode values. Expected {ColorMode.RGB.value} (RGB), got: "
            f"{[mode.value for mode in rgb_light.supported_color_modes]}"
        )

        # Binary light (on/off only) should support ON_OFF mode
        assert ColorMode.ON_OFF in binary_light.supported_color_modes, (
            f"Binary light missing ON_OFF color mode. Got: {binary_light.supported_color_modes}"
        )
        # Verify it's the actual enum value, not bit position
        assert ColorMode.ON_OFF.value in [
            mode.value for mode in binary_light.supported_color_modes
        ], (
            f"Binary light has wrong color mode values. Expected {ColorMode.ON_OFF.value} (ON_OFF), got: "
            f"{[mode.value for mode in binary_light.supported_color_modes]}"
        )

        async def wait_for_state_change(key: int, timeout: float = 1.0) -> Any:
            """Wait for a state change for the given entity key."""
            loop = asyncio.get_event_loop()
            state_futures[key] = loop.create_future()
            try:
                return await asyncio.wait_for(state_futures[key], timeout)
            finally:
                state_futures.pop(key, None)

        # Test all individual parameters first

        # Test 1: state only
        client.light_command(key=rgbcw_light.key, state=True)
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.state is True

        # Test 2: brightness only
        client.light_command(key=rgbcw_light.key, brightness=0.5)
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.brightness == pytest.approx(0.5)

        # Test 3: color_brightness only
        client.light_command(key=rgbcw_light.key, color_brightness=0.8)
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.color_brightness == pytest.approx(0.8)

        # Test 4-7: RGB values must be set together via rgb parameter
        client.light_command(key=rgbcw_light.key, rgb=(0.7, 0.3, 0.9))
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.red == pytest.approx(0.7, abs=0.1)
        assert state.green == pytest.approx(0.3, abs=0.1)
        assert state.blue == pytest.approx(0.9, abs=0.1)

        # Test 7: white value
        client.light_command(key=rgbcw_light.key, white=0.6)
        state = await wait_for_state_change(rgbcw_light.key)
        # White might need more tolerance or might not be directly settable
        if isinstance(state, LightState) and state.white is not None:
            assert state.white == pytest.approx(0.6, abs=0.1)

        # Test 8: color_temperature only
        client.light_command(key=rgbcw_light.key, color_temperature=300)
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.color_temperature == pytest.approx(300)

        # Test 9: cold_white only
        client.light_command(key=rgbcw_light.key, cold_white=0.8)
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.cold_white == pytest.approx(0.8)

        # Test 10: warm_white only
        client.light_command(key=rgbcw_light.key, warm_white=0.2)
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.warm_white == pytest.approx(0.2)

        # Test 11: transition_length with state change
        client.light_command(key=rgbcw_light.key, state=False, transition_length=0.1)
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.state is False

        # Test 12: flash_length
        client.light_command(key=rgbcw_light.key, state=True, flash_length=0.2)
        state = await wait_for_state_change(rgbcw_light.key)
        # Flash starts
        assert state.state is True
        # Wait for flash to end
        state = await wait_for_state_change(rgbcw_light.key)

        # Test 13: effect only - test all random effects
        # First ensure light is on
        client.light_command(key=rgbcw_light.key, state=True)
        state = await wait_for_state_change(rgbcw_light.key)

        # Test 13a: Default random effect (no name, gets default name "Random")
        client.light_command(key=rgbcw_light.key, effect="Random")
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.effect == "Random"

        # Test 13b: Slow random effect with long name
        client.light_command(
            key=rgbcw_light.key, effect="My Very Slow Random Effect With Long Name"
        )
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.effect == "My Very Slow Random Effect With Long Name"

        # Test 13c: Fast random effect with long name
        client.light_command(
            key=rgbcw_light.key, effect="My Fast Random Effect That Changes Quickly"
        )
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.effect == "My Fast Random Effect That Changes Quickly"

        # Test 13d: Random effect with medium length name
        client.light_command(
            key=rgbcw_light.key, effect="Random Effect With Medium Length Name Here"
        )
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.effect == "Random Effect With Medium Length Name Here"

        # Test 13e: Another random effect
        client.light_command(
            key=rgbcw_light.key,
            effect="Another Random Effect With Different Parameters",
        )
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.effect == "Another Random Effect With Different Parameters"

        # Test 13f: Yet another random effect
        client.light_command(
            key=rgbcw_light.key, effect="Yet Another Random Effect To Test Memory"
        )
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.effect == "Yet Another Random Effect To Test Memory"

        # Test 14: stop effect
        client.light_command(key=rgbcw_light.key, effect="None")
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.effect == "None"

        # Test 15: color_mode parameter
        client.light_command(
            key=rgbcw_light.key, state=True, color_mode=5
        )  # COLD_WARM_WHITE
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.state is True

        # Now test common combinations

        # Test 16: RGB combination (set_rgb) - RGB values get normalized
        client.light_command(key=rgbcw_light.key, rgb=(1.0, 0.0, 0.5))
        state = await wait_for_state_change(rgbcw_light.key)
        # RGB values get normalized - in this case red is already 1.0
        assert state.red == pytest.approx(1.0, abs=0.1)
        assert state.green == pytest.approx(0.0, abs=0.1)
        assert state.blue == pytest.approx(0.5, abs=0.1)

        # Test 17: Multiple RGB changes to test transitions
        client.light_command(key=rgbcw_light.key, rgb=(0.2, 0.8, 0.4))
        state = await wait_for_state_change(rgbcw_light.key)
        # RGB values get normalized so green (highest) becomes 1.0
        # Expected: (0.2/0.8, 0.8/0.8, 0.4/0.8) = (0.25, 1.0, 0.5)
        assert state.red == pytest.approx(0.25, abs=0.01)
        assert state.green == pytest.approx(1.0, abs=0.01)
        assert state.blue == pytest.approx(0.5, abs=0.01)

        # Test 18: State + brightness + transition
        client.light_command(
            key=rgbcw_light.key, state=True, brightness=0.7, transition_length=0.1
        )
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.state is True
        assert state.brightness == pytest.approx(0.7)

        # Test 19: RGB + brightness + color_brightness
        client.light_command(
            key=rgb_light.key,
            state=True,
            brightness=0.8,
            color_brightness=0.9,
            rgb=(0.2, 0.4, 0.6),
        )
        state = await wait_for_state_change(rgb_light.key)
        assert state.state is True
        assert state.brightness == pytest.approx(0.8)

        # Test 20: Color temp + cold/warm white
        client.light_command(
            key=rgbcw_light.key, color_temperature=250, cold_white=0.7, warm_white=0.3
        )
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.color_temperature == pytest.approx(250)

        # Test 21: Turn RGB light off
        client.light_command(key=rgb_light.key, state=False)
        state = await wait_for_state_change(rgb_light.key)
        assert state.state is False

        # Test color mode combinations to verify get_suitable_color_modes optimization

        # Test 22: White only mode
        client.light_command(key=rgbcw_light.key, state=True, white=0.5)
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.state is True

        # Test 23: Color temperature only mode
        client.light_command(key=rgbcw_light.key, state=True, color_temperature=300)
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.color_temperature == pytest.approx(300)

        # Test 24: Cold/warm white only mode
        client.light_command(
            key=rgbcw_light.key, state=True, cold_white=0.6, warm_white=0.4
        )
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.cold_white == pytest.approx(0.6)
        assert state.warm_white == pytest.approx(0.4)

        # Test 25: RGB only mode
        client.light_command(key=rgb_light.key, state=True, rgb=(0.5, 0.5, 0.5))
        state = await wait_for_state_change(rgb_light.key)
        assert state.state is True

        # Test 26: RGB + white combination
        client.light_command(
            key=rgbcw_light.key, state=True, rgb=(0.3, 0.3, 0.3), white=0.5
        )
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.state is True

        # Test 27: RGB + color temperature combination
        client.light_command(
            key=rgbcw_light.key, state=True, rgb=(0.4, 0.4, 0.4), color_temperature=280
        )
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.state is True

        # Test 28: RGB + cold/warm white combination
        client.light_command(
            key=rgbcw_light.key,
            state=True,
            rgb=(0.2, 0.2, 0.2),
            cold_white=0.5,
            warm_white=0.5,
        )
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.state is True

        # Test 29: White + color temperature combination
        client.light_command(
            key=rgbcw_light.key, state=True, white=0.6, color_temperature=320
        )
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.state is True

        # Test 30: No specific color parameters (tests default mode selection)
        client.light_command(key=rgbcw_light.key, state=True, brightness=0.75)
        state = await wait_for_state_change(rgbcw_light.key)
        assert state.state is True
        assert state.brightness == pytest.approx(0.75)

        # Final cleanup - turn all lights off
        for light in lights:
            client.light_command(
                key=light.key,
                state=False,
            )
            state = await wait_for_state_change(light.key)
            assert state.state is False
